#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import os
import sys
import time
import traceback
import shlex

import subprocess


def runproc(
    args,
    input=None,
    stdin=None,
    stdout=None,
    stderr=None,
    shell=False,
    timeout=None,
    pfunc=None,
):

    cwdq = os.getcwd()

    if sys.version_info >= (3, 0, 0):
        err = subprocess.run(
            args,
            stdin=stdin,
            input=input,
            stdout=stdout,
            stderr=stderr,
            shell=shell,
            timeout=timeout,
        )
    else:
        err = subprocess.call(
            args, stdin=stdin, stdout=stdout, stderr=stderr, shell=shell,
        )
        cwdh = os.getcwd()

    if pfunc:
        try:
            pfunc("PWD: %s and %s" % (cwdq, cwdh))
            pfunc("CMD: ", args)
            pfunc("ERR: ", err)
        except:
            pass

    return err


class LoopRun:
    def __init__(
        self,
        args,
        stdin=None,
        input=None,
        stdout=None,
        stderr=None,
        shell=False,
        cwd=None,
        timeout=None,
        loops=0,
        bfunc=None,
        afunc=None,
        qfunc=None,
        envs=None,
        loopwait=0,
        failtime=1,
        failmax=10,
        pfunc=None,
    ):

        # subprocess.run arguments
        self.args = args
        self.stdin = stdin
        self.input = input
        self.stdout = stdout
        self.stderr = stderr
        self.shell = shell
        self.cwd = cwd
        self.timeout = timeout

        # Other arguments
        self.loops = loops  # Count to call, 0 means forever
        self.bfunc = bfunc  # Function before call
        self.afunc = afunc  # Function after call
        self.qfunc = qfunc  # Function to test if quit the loop
        self.envs = envs  # dict of environments
        self.loopwait = loopwait  # Wait seconds before next loop
        self.failtime = failtime  # if run time < failtime, treat as failure
        self.failmax = failmax  # Quit loop run if fails > failmax
        self.pfunc = pfunc or self._fakepfunc  # Log output

    def _fakepfunc(self, *args):
        pass

    def go(self):
        # okLoops: quit with error == 0
        # ngLoops: quit with error != 0
        # goLoops: call times
        # exLoops: Exception

        okLoops = ngLoops = goLoops = exLoops = 0
        leftLoops = self.loops
        failsCnt = 0

        cmdLine = " ".join(self.args)

        while True:
            goLoops += 1

            if self.loops > 0:
                leftLoops -= 1
                if leftLoops == -1:
                    break

            self.pfunc(
                "LOOPS(GO:%d, ALL:%d, OK:%d, NG:%d, EX:%d) %s"
                % (goLoops, self.loops, okLoops, ngLoops, exLoops, cmdLine)
            )

            tm_beg = time.time()
            try:
                if self.cwd:
                    os.chdir(self.cwd)

                if self.envs:
                    for k, v in self.envs.items():
                        os.environ[k] = str(v)

                if self.bfunc:
                    self.bfunc()

                p = runproc(
                    self.args,
                    stdin=self.stdin,
                    input=self.input,
                    stdout=self.stdout,
                    stderr=self.stderr,
                    shell=self.shell,
                    timeout=self.timeout,
                    pfunc=self.pfunc,
                )
                if p == 0:
                    okLoops += 1
                else:
                    ngLoops += 1
            except:
                exLoops += 1
                p = None
                self.pfunc("EXCEPT: ", traceback.format_exc())

            finally:
                if self.afunc:
                    self.afunc(p)

                if self.qfunc and self.qfunc(p):
                    break

            tm_end = time.time()
            if tm_end - tm_beg < self.failtime:
                failsCnt += 1
            else:
                failsCnt = 0

            if failsCnt >= self.failmax:
                self.pfunc("Too frequently quit, break from loopRun.")
                break

            time.sleep(self.loopwait)

        self.pfunc("loopRun exit...")


def load_looprun(conf, curdir=None, pfunc=None):
    # s:/xxx/looprun/list=aaa;bbb
    #
    # s:/xxx/looprun/proc/aaa/cmd=echo a bc
    # s:/xxx/looprun/proc/aaa/cwd=
    # s:/xxx/looprun/proc/aaa/env/KEY=VAL

    curdir = curdir or "."
    envpat = "s:/" + conf.group + "/looprun/proc/%s/env/"

    infos = []
    names = conf.xget("looprun/list", "").split(";")
    for name in names:
        try:
            print("Name:", name)
            name = name.strip()
            if not name:
                continue

            cmd = shlex.split(conf.xget("looprun/proc/%s/cmd" % name, ""))
            if not cmd:
                continue

            dic = {}
            dic["name"] = name
            dic["cmd"] = cmd

            cwd = conf.xget("looprun/proc/%s/cwd" % name, "") or curdir
            dic["cwd"] = cwd

            dic["env"] = {}

            def do_env(key, val, cookie):
                # s:/xxx/looprun/proc/aaa/env/KEY=VAL
                if not key.startswith(envpat % name):
                    return

                segs = key.split("/")
                env = segs[-1]
                dic["env"][env] = val

            conf.foreach(do_env)
            infos.append(dic)
        except:
            if pfunc:
                pfunc("EXCEPT: ", traceback.format_exc())

    return infos


def call_looprun(info):
    cmd = info.get("cmd")
    cwd = info.get("cwd")
    envs = info.get("env")

    cmd = " ".join(cmd)
    lr = LoopRun(cmd, shell=True, cwd=cwd, envs=envs)
    lr.go()

    # Any process quit will indicated a fatal error.
    os._exit(1)


if __name__ == "__main__":
    from multiprocessing import Pool
    from mie.confcenter import XConfCenter

    try:
        cfgfile = sys.argv[1]
        group = sys.argv[2]
    except:
        message = """Loop run commands from conf file.

Usage:
    looprun.py cfgfile group

Example configure file:
    s:/aaa/looprun/list=aaa;bbb

    s:/aaa/looprun/proc/aaa/cmd=sleep 2; date
    s:/aaa/looprun/proc/aaa/cwd=

    s:/aaa/looprun/proc/bbb/cmd=sleep 1; echo `date ; echo hehe`
    s:/aaa/looprun/proc/bbb/cwd=/tmp
"""
        sys.stdout.write(message)
        sys.exit(0)

    conf = XConfCenter(group=group, rw_cfg=cfgfile)

    procInfos = load_looprun(conf)
    p = Pool(len(procInfos))

    p.map(call_looprun, procInfos)

# vim: sw=4 ts=4 sts=4 ai et
